﻿using System;
using System.Threading;
using System.Text;
using System.Drawing;
using System.IO.Ports;
using System.Collections.Generic;

//*****************************************************************************************
//                           LICENSE INFORMATION
//*****************************************************************************************
//   PCCom.SerialCommunication Version 1.0.0.0
//   Class file for managing serial port communication
//
//   Copyright (C) 2007  
//   Richard L. McCutchen 
//   Email: richard@psychocoder.net
//   Created: 20OCT07
//
//   This program is free software: you can redistribute it and/or modify
//   it under the terms of the GNU General Public License as published by
//   the Free Software Foundation, either version 3 of the License, or
//   (at your option) any later version.
//
//   This program is distributed in the hope that it will be useful,
//   but WITHOUT ANY WARRANTY; without even the implied warranty of
//   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//   GNU General Public License for more details.
//
//   You should have received a copy of the GNU General Public License
//   along with this program.  If not, see <http://www.gnu.org/licenses/>.
//*****************************************************************************************
namespace ECMCommMan
{
    public enum MessageType { Incoming, Outgoing, Normal, Warning, Error };

    interface IECMCommMan
    {
        void DisplayData(MessageType type, string msg);
        string ColumntoString(string sDataGridName, string sColumnName);
        void ChangeVersionNo(string sVersionNo);
        string ReadVersionNo();
    }
    
    partial class ECMCommunicationManager
    {
        #region Manager Enums
        /// <summary>
        /// enumeration to hold our transmission types
        /// </summary>
        public enum TransmissionType { Text, Hex, ECM, DASH };

        /// <summary>
        /// enumeration to hold our message types
        /// </summary>
        public enum MessageType { Incoming, Outgoing, Normal, Warning, Error };

        /// <summary>
        /// enumeration to hold our Command types the ECM can respond to
        /// </summary>
        public enum ECMCommandType : byte
        {
            ECMGet = 0x52,
            ECMSet = 0x57,
            ECMVersion = 0x56,
            ECMRunTimeData = 0x43
        };

        /// <summary>
        /// enumeration to hold our special function data that can be requested from the ECM via virtual page 0x20
        /// </summary>
        public enum ECMVirtualPageFunction : byte
        {
            ClearCodes = 0x01,
            FrontCoil = 0x02,
            RearCoil = 0x03,
            Tachometer = 0x04,
            FuelPump = 0x05,
            FrontInj = 0x06,
            RearInj = 0x07,
            TPSReset = 0x08,
            Fan = 0x09,
            ExhaustValve = 0x0A
        };
        #endregion

        #region Manager Variables
        //property variables
        private IECMCommMan m_parent = null;
        private string m_baudRate = string.Empty;
        private string m_parity = string.Empty;
        private string m_stopBits = string.Empty;
        private string m_dataBits = string.Empty;
        private string m_portName = string.Empty;
        private TransmissionType m_transType;
        private byte[] bECMResponse;

        //global manager variables
        protected SerialPort comPort = new SerialPort();
        #endregion

        #region Manager Properties
        /// <summary>
        /// Property to hold the BaudRate
        /// of our manager class
        /// </summary>
        public string BaudRate
        {
            get { return m_baudRate; }
            set { m_baudRate = value; }
        }

        /// <summary>
        /// property to hold the Parity
        /// of our manager class
        /// </summary>
        public string Parity
        {
            get { return m_parity; }
            set { m_parity = value; }
        }

        /// <summary>
        /// property to hold the StopBits
        /// of our manager class
        /// </summary>
        public string StopBits
        {
            get { return m_stopBits; }
            set { m_stopBits = value; }
        }

        /// <summary>
        /// property to hold the DataBits
        /// of our manager class
        /// </summary>
        public string DataBits
        {
            get { return m_dataBits; }
            set { m_dataBits = value; }
        }

        /// <summary>
        /// property to hold the PortName
        /// of our manager class
        /// </summary>
        public string PortName
        {
            get { return m_portName; }
            set { m_portName = value; }
        }

        /// <summary>
        /// property to hold our TransmissionType
        /// of our manager class
        /// </summary>
        public TransmissionType CurrentTransmissionType
        {
            get { return m_transType; }
            set { m_transType = value; }
        }
        #endregion

        #region Manager Constructors
        /// <summary>
        /// Constructor to set the properties of our
        /// serial port communicator to nothing
        /// </summary>
        public ECMCommunicationManager(IECMCommMan parent)
        {
            m_parent = parent;
            m_baudRate = string.Empty;
            m_parity = string.Empty;
            m_stopBits = string.Empty;
            m_dataBits = string.Empty;
            m_portName = string.Empty;
            //add event handler
            comPort.DataReceived += new SerialDataReceivedEventHandler(comPort_DataReceived);
        }
        #endregion

        #region WriteData
        public void WriteData(dynamic msg)
        {
            switch (CurrentTransmissionType)
            {
                case TransmissionType.Text:
                    //first make sure the port is open
                    //if its not open then open it
                    if (!(comPort.IsOpen == true)) comPort.Open();
                    //send the message to the port
                    comPort.Write(msg);
                    //display the message
                    m_parent.DisplayData(ECMCommMan.MessageType.Outgoing, "Sent Data: " + msg + "\n");
                    break;
                case TransmissionType.Hex:
                case TransmissionType.ECM:
                case TransmissionType.DASH:
                    try
                    {
                        //convert the message to byte array
                        //byte[] newMsg = HexToByte(msg);
                        //send the message to the port
                        comPort.Write(msg, 0, msg.Length);
                        //convert back to hex and display
                        m_parent.DisplayData(ECMCommMan.MessageType.Outgoing, "Sent Data: " + ByteToHex(msg) + "\n");
                    }
                    catch (FormatException ex)
                    {
                        //display error message
                        m_parent.DisplayData(ECMCommMan.MessageType.Error, ex.Message);
                    }
                    finally
                    {
                        //_displayWindow.SelectAll();
                    }
                    break;
                default:
                    //first make sure the port is open
                    //if its not open then open it
                    if (!(comPort.IsOpen == true)) comPort.Open();
                    //send the message to the port
                    comPort.Write(msg);
                    //display the message
                    m_parent.DisplayData(ECMCommMan.MessageType.Outgoing, msg + "\n");
                    break;
            }
        }
        #endregion

        #region HexToByte
        /// <summary>
        /// method to convert hex string into a byte array
        /// </summary>
        /// <param name="msg">string to convert</param>
        /// <returns>a byte array</returns>
        public byte[] HexToByte(string msg)
        {
            //remove any spaces from the string
            msg = msg.Replace(" ", "");
            //create a byte array the length of the
            //divided by 2 (Hex is 2 characters in length)
            byte[] comBuffer = new byte[msg.Length / 2];
            //loop through the length of the provided string
            if (msg.Length % 2 != 0)
            {
                m_parent.DisplayData(ECMCommMan.MessageType.Error, "Could not convert last number into Byte, only 4 bit available\n");
            }
            else
            {
                for (int i = 0; i < msg.Length; i += 2)
                {
                    //convert each set of 2 characters to a byte
                    //and add to the array
                    comBuffer[i / 2] = (byte)Convert.ToByte(msg.Substring(i, 2), 16);
                }
            }
            //return the array
            return comBuffer;
        }
        #endregion

        #region ByteToHex
        /// <summary>
        /// method to convert a byte array into a hex string
        /// </summary>
        /// <param name="comByte">byte array to convert</param>
        /// <returns>a hex string</returns>
        public string ByteToHex(byte[] comByte)
        {
            //create a new StringBuilder object
            StringBuilder builder = new StringBuilder(comByte.Length * 3);
            //loop through each byte in the array
            foreach (byte data in comByte)
                //convert the byte to a string and add to the stringbuilder
                builder.Append(Convert.ToString(data, 16).PadLeft(2, '0').PadRight(3, ' '));
            //return the converted value
            return builder.ToString().ToUpper();
        }
        #endregion

        #region ASCIIStringToHex
        /// <summary>
        /// method to convert a ascii string into a hex string
        /// </summary>
        /// <param name="comByte">byte array to convert</param>
        /// <returns>a hex string</returns>
        public string AsciiToHex(string sAsciiString)
        {
            string sHex = "";
            foreach (char c in sAsciiString)
            {
                int tmp = c;
                sHex += String.Format("{0:x2}", (uint)System.Convert.ToUInt32(tmp.ToString()));
            }
            return sHex;
        }
        #endregion

        #region CalculateCeckSum
        /// <summary>
        /// method to calculate the message check sum in XOR format
        /// </summary>
        /// <param name="bArray">byte array with values for Check Sum calculation </param>
        /// <returns>a byte containing check sum</returns>
        public byte CalculateCheckSum(byte[] bArray)
        {
            byte bCheckSumCalc = 0x00;
            int nArrayCount = 0;
            for (nArrayCount = 2; nArrayCount < bArray.Length - 1; nArrayCount++)
            {
                bCheckSumCalc ^= bArray[nArrayCount];
            }
            return bCheckSumCalc;
        }
        #endregion

        #region OpenPort
        public bool OpenPort()
        {
            try
            {
                //first check if the port is already open
                //if its open then close it
                if (comPort.IsOpen == true) comPort.Close();

                //set the properties of our SerialPort Object
                comPort.BaudRate = int.Parse(m_baudRate);    //BaudRate
                comPort.DataBits = int.Parse(m_dataBits);    //DataBits
                comPort.StopBits = (StopBits)Enum.Parse(typeof(StopBits), m_stopBits);    //StopBits
                comPort.Parity = (Parity)Enum.Parse(typeof(Parity), m_parity);    //Parity
                comPort.PortName = m_portName;   //PortName
                //now open the port
                comPort.Open();
                //display message
                m_parent.DisplayData(ECMCommMan.MessageType.Normal, "Port " + PortName + " opened at " + DateTime.Now + "\n");
                //return true
                return true;
            }
            catch (Exception ex)
            {
                m_parent.DisplayData(ECMCommMan.MessageType.Error, ex.Message);
                return false;
            }
        }
        #endregion

        #region ClosePort
        public bool ClosePort()
        {
            //now close the port
            comPort.Close();
            //display message
            m_parent.DisplayData(ECMCommMan.MessageType.Normal, "Port " + PortName + " closed at " + DateTime.Now + "\n");
            //return true
            return true;
        }
        #endregion

        #region GetParityValues
        public List<string> GetParityValues()
        {
            List<string> sParityValues = new List<string>();
            foreach (string str in Enum.GetNames(typeof(Parity)))
            {
                sParityValues.Add(str);
            }
            return sParityValues;
        }
        #endregion

        #region GetStopBitValues
        public List<string> GetStopBitValues()
        {
            List<string> sStopBitValues = new List<string>();
            foreach (string str in Enum.GetNames(typeof(StopBits)))
            {
                sStopBitValues.Add(str);
            }
            return sStopBitValues;
        }
        #endregion

        #region FindAvailablePorts
        public List<string> FindAvailablePorts()
        {
            List<string> sAvailablePortList = new List<string>();
            foreach (string str in SerialPort.GetPortNames())
            {
                sAvailablePortList.Add(str);
            }
            return sAvailablePortList;
        }
        #endregion

        #region comPort_DataReceived
        /// <summary>
        /// method that will be called when theres data waiting in the buffer
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        protected void comPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
        {
            Thread.Sleep(150);
            
            //determine the mode the user selected (binary/string)
            switch (CurrentTransmissionType)
            {
                //user chose string
                case TransmissionType.Text:
                    //read data waiting in the buffer
                    string msg = comPort.ReadExisting();
                    //display the data to the user
                    m_parent.DisplayData(ECMCommMan.MessageType.Incoming, msg + "\n");
                    break;
                //user chose binary or ECM
                case TransmissionType.Hex:
                case TransmissionType.ECM:
                case TransmissionType.DASH:
                    //retrieve number of bytes in the buffer
                    int bytes = comPort.BytesToRead;
                    //create a byte array to hold the awaiting data
                    byte[] comBuffer = new byte[bytes];
                    //read the data and store it
                    comPort.Read(comBuffer, 0, bytes);
                    //display the data to the user
                    if (CurrentTransmissionType == TransmissionType.ECM)
                    {
                        m_parent.DisplayData(ECMCommMan.MessageType.Incoming, "Dash Request: " + ByteToHex(comBuffer) + "\n");
                        ECMAnalyseDashRequest(comBuffer);
                    }
                    else
                    {
                        if (CurrentTransmissionType == TransmissionType.DASH)
                        {
                            m_parent.DisplayData(ECMCommMan.MessageType.Incoming, "ECM Reply: " + ByteToHex(comBuffer) + "\n\n");
                            //ECMAnalyseECMResponse(comBuffer);
                        }
                        else
                        {
                            m_parent.DisplayData(ECMCommMan.MessageType.Incoming, "Received Data: " + ByteToHex(comBuffer) + "\n");
                        }
                    }
                    break;
                default:
                    //read data waiting in the buffer
                    string str = comPort.ReadExisting();
                    //display the data to the user
                    m_parent.DisplayData(ECMCommMan.MessageType.Incoming, str + "\n");
                    break;
            }
        }
        #endregion

        #region Analyse Dash Request
        /// <summary>
        /// method to find out what has been requested from the ECM
        /// </summary>
        /// <param name="receivedByte">byte array that contains Command Code in the 7th Byte</param>
        /// <returns>a byte in hex containing the Command code</returns>
        protected byte ECMAnalyseDashRequest(byte[] receivedByte)
        {
            if (receivedByte.Length > 7)
            {
                switch (receivedByte[6])
                {
                    case (byte)ECMCommandType.ECMGet:
                        m_parent.DisplayData(ECMCommMan.MessageType.Error, "ECMGet is not supported yet" + "\n");
                        break;
                    case (byte)ECMCommandType.ECMSet:
                        m_parent.DisplayData(ECMCommMan.MessageType.Error, "ECMSet is not supported yet" + "\n");
                        break;
                    case (byte)ECMCommandType.ECMVersion:
                        m_parent.DisplayData(ECMCommMan.MessageType.Outgoing, "Sending ECM Version Info\n");
                        ECMVersionNoResponse();
                        break;
                    case (byte)ECMCommandType.ECMRunTimeData:
                        m_parent.DisplayData(ECMCommMan.MessageType.Outgoing, "Sending ECM Run Time Data\n");
                        ECMRunTimeResponse();
                        break;
                    default:
                        m_parent.DisplayData(ECMCommMan.MessageType.Error, "Unknown message" + "\n");
                        break;
                }
                return receivedByte[6];
            }
            else
            {
                m_parent.DisplayData(ECMCommMan.MessageType.Error, "Dash Request not valid\n");
                return 0;
            }
        }
        #endregion

        #region Build Runtime Response
        /// <summary>
        /// method to find out what has been requested from the ECM
        /// </summary>
        /// <param name="receivedByte">byte array that contains Command Code in the 7th Byte</param>
        /// <returns>a byte in hex containing the Command code</returns>
        protected void ECMRunTimeResponse()
        {
            string s = m_parent.ColumntoString("dgRunTimeData", "Hex_Data");

            byte[] bCheckSum = HexToByte(s);

            bCheckSum[bCheckSum.Length - 1] = CalculateCheckSum(bCheckSum);

            WriteData(bCheckSum);
        }
        #endregion

        #region Version No Response
        /// <summary>
        /// method to find out what has been requested from the ECM
        /// </summary>
        /// <param name="receivedByte">byte array that contains Command Code in the 7th Byte</param>
        /// <returns>a byte in hex containing the Command code</returns>
        protected void ECMVersionNoResponse()
        {
            string s = m_parent.ReadVersionNo();
            string sHex = AsciiToHex(s);

            bECMResponse = new byte[26]{ 0x01, 0x42, 0x00, 0x13, 0xFF, 0x02, 0x06, 0x59, 0x59, 0x59, 0x59, 0x59, 0x39, 0x39, 0x39, 0x20, 0x31, 0x31, 0x2D, 0x32, 0x32, 0x2D, 0x33, 0x33, 0x03, 0xEB};

            byte[] bVersionNo = HexToByte(sHex);

            for (int i = 0; i < bVersionNo.Length; i++)
            {

                bECMResponse[i + 7] = bVersionNo[i];
            }
            
            bECMResponse[bECMResponse.Length - 1] = CalculateCheckSum(bECMResponse);

            WriteData(bECMResponse);
        }
        #endregion
    }
}
